use anchor_lang::prelude::*;
use anchor_spl::{
      associated_token::AssociatedToken,  token_interface::{
        transfer_checked, CloseAccount, Mint, TokenAccount, TokenInterface, TransferChecked,close_account}

    };

use crate::{transfer_tokens, Offer};



#[derive(Accounts)]
//#[instruction(id: u64)]
pub struct TakeOffer<'info>{
    #[account(mut)]
    pub taker: Signer<'info>,

    #[account(mut)]
    pub maker: SystemAccount<'info>,

    //#[account(mint::token_program = token_program)]
    pub token_mint_a: InterfaceAccount<'info, Mint>,

    // #[account(mint::token_program = token_program)]
    pub token_mint_b: InterfaceAccount<'info, Mint>,

    #[account(
        // take may not have any balance of this account,  when they havn't have token-a previously. when we move the tokens from the vault into token account a ,
        // we'll make a token account a for them.    就像  alice 向  bob 招行 转 日元， bob 招行上没有过日元， 这里转过来就要初始化一个日元账户 以供接收。
        init_if_needed,  
        payer = taker, 
        associated_token::mint = token_mint_a,
        associated_token::authority = taker,
        associated_token::token_program = token_program
    )]
    pub taker_token_account_a: Box<InterfaceAccount<'info,TokenAccount>>,

    #[account(
        mut,
        associated_token::mint = token_mint_b,
        associated_token::authority = taker,
        associated_token::token_program = token_program
    )]
     pub taker_token_account_b: Box<InterfaceAccount<'info,TokenAccount>>,

     #[account(
        // 创建者  的b  token 如没有， 也将初始化一一个了token的账户   整个故事的场景  就是  maker 拿自己的 一定数量的 token a   , 交换 taker 后中 另一定数量的  token b
        init_if_needed,  
        payer = maker,
        associated_token::mint = token_mint_b,
        associated_token::authority = maker,
        associated_token::token_program = token_program
    )]
     pub maker_token_account_b: Box<InterfaceAccount<'info,TokenAccount>>,
    #[account(
        // we're gonna close this offer account when the offer if token
        mut,
        
        // we will return SOL to maker , cause the maker made the offer account in the first place. 
        close = maker,
        has_one = maker,
        has_one = token_mint_a,
        has_one = token_mint_b,
        seeds = [b"offer", maker.key().as_ref(), offer.id.to_le_bytes().as_ref()],
        bump
    )]
    pub offer: Account<'info,Offer>,


    // we need the vault because obviously this take offer. is going to move tokens form the vault
    #[account(
        mut,
        associated_token::mint = token_mint_a,
        associated_token::authority = offer,  // 保险箱被 offer 控制 ， 不是被  maker 或 taker 控制。  
        associated_token::token_program = token_program
    )] 
    pub vault: InterfaceAccount<'info, TokenAccount>,  // 这里所有的 TokenAccount 用的是 anchor_spl::token_interface 中的， 是为了与旧版兼容  以及token的扩展性


    
    pub system_program : Program<'info,System>,
    pub associated_token_program: Program<'info,AssociatedToken>,

    pub token_program: Interface<'info,TokenInterface>,

}

// 从taker的 token b 账户 直接转  到maker 的token b 账户中
pub fn send_wanted_tokens_to_maker(ctx: &Context<TakeOffer>) ->Result<()>{
     transfer_tokens(&ctx.accounts.taker_token_account_b,
         &ctx.accounts.maker_token_account_b, 
         &ctx.accounts.offer.token_b_wanted_amount, 
         &ctx.accounts.token_mint_b, 
         &ctx.accounts.taker, 
        &ctx.accounts.token_program,
    )
}

// 这个不是一个普通的转账 ， 是从金库转账，  这是PDA的转移， by our offer account itself. 用的方法是  另一个叫   checked
pub fn withdraw_and_close_vault(ctx: Context<TakeOffer>) ->Result<()>{
    // 
    let seeds = &[b"offer",
                       ctx.accounts.maker.to_account_info().key.as_ref(), 
                        &ctx.accounts.offer.id.to_le_bytes()[..],
                        &[ctx.accounts.offer.bump]];

    // 放入一个更大的签名数组中
    let signer_seeds = [&seeds[..]];
    let accounts = TransferChecked{
        from : ctx.accounts.vault.to_account_info(),
        to: ctx.accounts.taker_token_account_a.to_account_info(),
        mint: ctx.accounts.token_mint_a.to_account_info(),
        authority: ctx.accounts.offer.to_account_info(),
    };

    // all we're doing here  is combining the accounts,   which are effectively the options for TransferChecked,
    let cpi_context = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(), 
        accounts,
         &signer_seeds,);

         transfer_checked(cpi_context,
             ctx.accounts.vault.amount, 
             ctx.accounts.token_mint_a.decimals,
            )?;  // 其实问号的意思 就是， 如果有错误就抛出来， 如果不加会怎样？ 我感觉也是一样的， 只是写了， 是让程序员确认

            let accounts = CloseAccount{
                account: ctx.accounts.vault.to_account_info(),
                destination: ctx.accounts.taker.to_account_info(),
                authority: ctx.accounts.offer.to_account_info(),
            };

            let cpi_context = CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(), 
                accounts, 
                &signer_seeds);
           
           close_account(cpi_context)
     
}